{
 "cells": [
  {
   "metadata": {
    "tags": [
     "Logistic回归鸢尾花数据"
    ],
    "ExecuteTime": {
     "end_time": "2025-04-03T07:05:59.290558Z",
     "start_time": "2025-04-03T07:05:57.857806Z"
    }
   },
   "cell_type": "code",
   "source": [
    "\n",
    "from sklearn.datasets import load_iris\n",
    "from sklearn.model_selection import train_test_split\n",
    "from sklearn.linear_model import LogisticRegression\n",
    "from sklearn.metrics import confusion_matrix, accuracy_score, classification_report\n",
    "import seaborn as sns\n",
    "import matplotlib.pyplot as plt"
   ],
   "id": "753924186d80db47",
   "outputs": [],
   "execution_count": 1
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "Logistic回归鸢尾花数据集代码实现",
   "id": "7e7959832568118a"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-04-03T07:06:00.182497Z",
     "start_time": "2025-04-03T07:05:59.327419Z"
    }
   },
   "cell_type": "code",
   "source": [
    "iris = load_iris()\n",
    "X = iris.data  # 特征数据\n",
    "y = iris.target \n",
    "\n",
    "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)\n",
    "\n",
    "model = LogisticRegression(max_iter=200,  solver='lbfgs')\n",
    "model.fit(X_train, y_train)\n",
    "\n",
    "y_pred = model.predict(X_test)\n",
    "\n",
    "accuracy = accuracy_score(y_test, y_pred)\n",
    "print(f\"模型准确率: {accuracy:.2f}\")\n",
    "\n",
    "print(\"分类报告：\")\n",
    "print(classification_report(y_test, y_pred))\n",
    "\n",
    "cm = confusion_matrix(y_test, y_pred)\n",
    "\n",
    "plt.figure(figsize=(8, 6))\n",
    "sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', \n",
    "            xticklabels=iris.target_names, yticklabels=iris.target_names)\n",
    "plt.title('Confusion Matrix Heatmap')\n",
    "plt.xlabel('Predicted Label')\n",
    "plt.ylabel('True Label')\n",
    "plt.show()"
   ],
   "id": "b6144143e1f170eb",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "模型准确率: 1.00\n",
      "分类报告：\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "           0       1.00      1.00      1.00        19\n",
      "           1       1.00      1.00      1.00        13\n",
      "           2       1.00      1.00      1.00        13\n",
      "\n",
      "    accuracy                           1.00        45\n",
      "   macro avg       1.00      1.00      1.00        45\n",
      "weighted avg       1.00      1.00      1.00        45\n",
      "\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<Figure size 800x600 with 2 Axes>"
      ],
      "image/png": "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91fPnj0lST///LNatGihypUrq1u3bho/frx1uvmfU88zZ87UwIEDNXr0aD3//POqUaOGPvvsM+vx/j31vGjRIgUGBsrX11ddu3bV+fPnJUk3b95UUFCQatSooYoVK6pJkybaunVrxn04QCqQKD5lTpw4oalTp2r06NH67rvvVLVqVb377ru6ffu23n77besf5SFDhmj27Nlat26dChYsaP0S+/nnn+Xr66s1a9Zo/Pjx6tGjh9atW6eaNWuqe/fuunz5spKSkvTuu++qSZMm+u677/TOO+9o7NixioiIUHJysnr27KnChQvrm2++0VdffaWkpCRNmzbNzp8MMkLz5s21fft23b17V9K9m9Fv375dzZo1U9++feXp6am1a9cqODhYGzZs0Ny5c63vPXTokEqVKqWVK1cqICBAgwcPVrly5bRx40ZNnDhR8+fP144dO1Icc/r06Vq7dq0mTZqkFStW6OrVqxo9erQkafTo0dqyZYumTJmir776SomJierdu7eSk5NTjNO3b1+dPHlSc+fO1aJFixQZGWmz5uyPP/6Q2WzWmjVr9NJLL6X3R4fH5H7naPPmzVP03b59u7788ku9//77On/+vHr16qWmTZtq3bp1qlSp0n+u4/7hhx/k4uKitWvXqmvXrvrggw905syZFP2++uorhYSE6P3339fatWuVI0cOvfPOO5KkiRMn6syZM1q4cKE2btyoqlWravjw4TKbzen0aQAPwYKnyubNmy0VK1a0nDp1ymKxWCy3bt2y/PLLL5Yvv/zS0rJlS5u+S5cutbbt3bvXUqZMGetrr776quXDDz+06d+mTRvLBx98YLl+/bqlTJkylpUrV1pf27NnjyU2NtZy69Yty2effWa5deuW9bUvv/zSUr9+/XT/XWF/CQkJFj8/P8uuXbssFovFsm3bNkuNGjUsv/zyi+WFF16wJCUlWftu27bNUr16dYvFYrGsXr3a4u3tbblz54719eeff97yySefWN9z8OBBy59//mmxWCyWevXqWVavXm1JTk62VK9e3bJ69Wrr+06fPm2ZMWOGJTY21lK2bFlrLBaLxXL9+nWLj4+PZefOnZbz589bypQpYzl//rzl5MmTljJlyliioqKsfSMiIixlypSxREZGWv//ISIiIgM+NTxO9ztHz507Zz0fLBaLpUyZMpYvvvjC+r4PP/zQ0r59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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 2
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "调用Ml库实现模拟训练",
   "id": "255b7821112af477"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-28T03:39:33.161947Z",
     "start_time": "2025-03-28T03:37:08.618699Z"
    }
   },
   "cell_type": "code",
   "source": [
    "import numpy as np\n",
    "from sklearn.datasets import fetch_openml\n",
    "from sklearn.model_selection import train_test_split\n",
    "from sklearn.preprocessing import StandardScaler\n",
    "\n",
    "# 加载MNIST数据集\n",
    "mnist = fetch_openml('mnist_784', version=1)\n",
    "X, y = mnist.data, mnist.target.astype(np.int8)\n",
    "\n",
    "# 数据标准化\n",
    "scaler = StandardScaler()\n",
    "X_scaled = scaler.fit_transform(X)\n",
    "\n",
    "X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.2, random_state=42)\n"
   ],
   "id": "ea399c534455f686",
   "outputs": [
    {
     "ename": "KeyboardInterrupt",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "\u001B[1;31m---------------------------------------------------------------------------\u001B[0m",
      "\u001B[1;31mKeyboardInterrupt\u001B[0m                         Traceback (most recent call last)",
      "Cell \u001B[1;32mIn[4], line 7\u001B[0m\n\u001B[0;32m      4\u001B[0m \u001B[38;5;28;01mfrom\u001B[39;00m \u001B[38;5;21;01msklearn\u001B[39;00m\u001B[38;5;21;01m.\u001B[39;00m\u001B[38;5;21;01mpreprocessing\u001B[39;00m \u001B[38;5;28;01mimport\u001B[39;00m StandardScaler\n\u001B[0;32m      6\u001B[0m \u001B[38;5;66;03m# 加载MNIST数据集\u001B[39;00m\n\u001B[1;32m----> 7\u001B[0m mnist \u001B[38;5;241m=\u001B[39m \u001B[43mfetch_openml\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;124;43m'\u001B[39;49m\u001B[38;5;124;43mmnist_784\u001B[39;49m\u001B[38;5;124;43m'\u001B[39;49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mversion\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;241;43m1\u001B[39;49m\u001B[43m)\u001B[49m\n\u001B[0;32m      8\u001B[0m X, y \u001B[38;5;241m=\u001B[39m mnist\u001B[38;5;241m.\u001B[39mdata, mnist\u001B[38;5;241m.\u001B[39mtarget\u001B[38;5;241m.\u001B[39mastype(np\u001B[38;5;241m.\u001B[39mint8)\n\u001B[0;32m     10\u001B[0m \u001B[38;5;66;03m# 数据标准化\u001B[39;00m\n",
      "File \u001B[1;32mD:\\python\\python-3.12.4\\Lib\\site-packages\\sklearn\\utils\\_param_validation.py:216\u001B[0m, in \u001B[0;36mvalidate_params.<locals>.decorator.<locals>.wrapper\u001B[1;34m(*args, **kwargs)\u001B[0m\n\u001B[0;32m    210\u001B[0m \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[0;32m    211\u001B[0m     \u001B[38;5;28;01mwith\u001B[39;00m config_context(\n\u001B[0;32m    212\u001B[0m         skip_parameter_validation\u001B[38;5;241m=\u001B[39m(\n\u001B[0;32m    213\u001B[0m             prefer_skip_nested_validation \u001B[38;5;129;01mor\u001B[39;00m global_skip_validation\n\u001B[0;32m    214\u001B[0m         )\n\u001B[0;32m    215\u001B[0m     ):\n\u001B[1;32m--> 216\u001B[0m         \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mfunc\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43margs\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m    217\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m InvalidParameterError \u001B[38;5;28;01mas\u001B[39;00m e:\n\u001B[0;32m    218\u001B[0m     \u001B[38;5;66;03m# When the function is just a wrapper around an estimator, we allow\u001B[39;00m\n\u001B[0;32m    219\u001B[0m     \u001B[38;5;66;03m# the function to delegate validation to the estimator, but we replace\u001B[39;00m\n\u001B[0;32m    220\u001B[0m     \u001B[38;5;66;03m# the name of the estimator by the name of the function in the error\u001B[39;00m\n\u001B[0;32m    221\u001B[0m     \u001B[38;5;66;03m# message to avoid confusion.\u001B[39;00m\n\u001B[0;32m    222\u001B[0m     msg \u001B[38;5;241m=\u001B[39m re\u001B[38;5;241m.\u001B[39msub(\n\u001B[0;32m    223\u001B[0m         \u001B[38;5;124mr\u001B[39m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mparameter of \u001B[39m\u001B[38;5;124m\\\u001B[39m\u001B[38;5;124mw+ must be\u001B[39m\u001B[38;5;124m\"\u001B[39m,\n\u001B[0;32m    224\u001B[0m         \u001B[38;5;124mf\u001B[39m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mparameter of \u001B[39m\u001B[38;5;132;01m{\u001B[39;00mfunc\u001B[38;5;241m.\u001B[39m\u001B[38;5;18m__qualname__\u001B[39m\u001B[38;5;132;01m}\u001B[39;00m\u001B[38;5;124m must be\u001B[39m\u001B[38;5;124m\"\u001B[39m,\n\u001B[0;32m    225\u001B[0m         \u001B[38;5;28mstr\u001B[39m(e),\n\u001B[0;32m    226\u001B[0m     )\n",
      "File \u001B[1;32mD:\\python\\python-3.12.4\\Lib\\site-packages\\sklearn\\datasets\\_openml.py:1130\u001B[0m, in \u001B[0;36mfetch_openml\u001B[1;34m(name, version, data_id, data_home, target_column, cache, return_X_y, as_frame, n_retries, delay, parser, read_csv_kwargs)\u001B[0m\n\u001B[0;32m   1128\u001B[0m \u001B[38;5;66;03m# obtain the data\u001B[39;00m\n\u001B[0;32m   1129\u001B[0m url \u001B[38;5;241m=\u001B[39m _DATA_FILE\u001B[38;5;241m.\u001B[39mformat(data_description[\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mfile_id\u001B[39m\u001B[38;5;124m\"\u001B[39m])\n\u001B[1;32m-> 1130\u001B[0m bunch \u001B[38;5;241m=\u001B[39m \u001B[43m_download_data_to_bunch\u001B[49m\u001B[43m(\u001B[49m\n\u001B[0;32m   1131\u001B[0m \u001B[43m    \u001B[49m\u001B[43murl\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m   1132\u001B[0m \u001B[43m    \u001B[49m\u001B[43mreturn_sparse\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m   1133\u001B[0m \u001B[43m    \u001B[49m\u001B[43mdata_home\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m   1134\u001B[0m \u001B[43m    \u001B[49m\u001B[43mas_frame\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;28;43mbool\u001B[39;49m\u001B[43m(\u001B[49m\u001B[43mas_frame\u001B[49m\u001B[43m)\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m   1135\u001B[0m \u001B[43m    \u001B[49m\u001B[43mopenml_columns_info\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mfeatures_list\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m   1136\u001B[0m \u001B[43m    \u001B[49m\u001B[43mshape\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mshape\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m   1137\u001B[0m \u001B[43m    \u001B[49m\u001B[43mtarget_columns\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mtarget_columns\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m   1138\u001B[0m \u001B[43m    \u001B[49m\u001B[43mdata_columns\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mdata_columns\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m   1139\u001B[0m \u001B[43m    \u001B[49m\u001B[43mmd5_checksum\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mdata_description\u001B[49m\u001B[43m[\u001B[49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[38;5;124;43mmd5_checksum\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[43m]\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m   1140\u001B[0m \u001B[43m    \u001B[49m\u001B[43mn_retries\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mn_retries\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m   1141\u001B[0m \u001B[43m    \u001B[49m\u001B[43mdelay\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mdelay\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m   1142\u001B[0m \u001B[43m    \u001B[49m\u001B[43mparser\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mparser_\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m   1143\u001B[0m \u001B[43m    \u001B[49m\u001B[43mread_csv_kwargs\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mread_csv_kwargs\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m   1144\u001B[0m \u001B[43m\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m   1146\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m return_X_y:\n\u001B[0;32m   1147\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m bunch\u001B[38;5;241m.\u001B[39mdata, bunch\u001B[38;5;241m.\u001B[39mtarget\n",
      "File \u001B[1;32mD:\\python\\python-3.12.4\\Lib\\site-packages\\sklearn\\datasets\\_openml.py:684\u001B[0m, in \u001B[0;36m_download_data_to_bunch\u001B[1;34m(url, sparse, data_home, as_frame, openml_columns_info, data_columns, target_columns, shape, md5_checksum, n_retries, delay, parser, read_csv_kwargs)\u001B[0m\n\u001B[0;32m    680\u001B[0m     \u001B[38;5;28;01mfrom\u001B[39;00m \u001B[38;5;21;01mpandas\u001B[39;00m\u001B[38;5;21;01m.\u001B[39;00m\u001B[38;5;21;01merrors\u001B[39;00m \u001B[38;5;28;01mimport\u001B[39;00m ParserError\n\u001B[0;32m    682\u001B[0m     no_retry_exception \u001B[38;5;241m=\u001B[39m ParserError\n\u001B[1;32m--> 684\u001B[0m X, y, frame, categories \u001B[38;5;241m=\u001B[39m \u001B[43m_retry_with_clean_cache\u001B[49m\u001B[43m(\u001B[49m\n\u001B[0;32m    685\u001B[0m \u001B[43m    \u001B[49m\u001B[43murl\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mdata_home\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mno_retry_exception\u001B[49m\n\u001B[0;32m    686\u001B[0m \u001B[43m\u001B[49m\u001B[43m)\u001B[49m\u001B[43m(\u001B[49m\u001B[43m_load_arff_response\u001B[49m\u001B[43m)\u001B[49m\u001B[43m(\u001B[49m\n\u001B[0;32m    687\u001B[0m \u001B[43m    \u001B[49m\u001B[43murl\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m    688\u001B[0m \u001B[43m    \u001B[49m\u001B[43mdata_home\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m    689\u001B[0m \u001B[43m    \u001B[49m\u001B[43mparser\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mparser\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m    690\u001B[0m \u001B[43m    \u001B[49m\u001B[43moutput_type\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43moutput_type\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m    691\u001B[0m \u001B[43m    \u001B[49m\u001B[43mopenml_columns_info\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mfeatures_dict\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m    692\u001B[0m \u001B[43m    \u001B[49m\u001B[43mfeature_names_to_select\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mdata_columns\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m    693\u001B[0m \u001B[43m    \u001B[49m\u001B[43mtarget_names_to_select\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mtarget_columns\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m    694\u001B[0m \u001B[43m    \u001B[49m\u001B[43mshape\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mshape\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m    695\u001B[0m \u001B[43m    \u001B[49m\u001B[43mmd5_checksum\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mmd5_checksum\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m    696\u001B[0m \u001B[43m    \u001B[49m\u001B[43mn_retries\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mn_retries\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m    697\u001B[0m \u001B[43m    \u001B[49m\u001B[43mdelay\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mdelay\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m    698\u001B[0m \u001B[43m    \u001B[49m\u001B[43mread_csv_kwargs\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mread_csv_kwargs\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m    699\u001B[0m \u001B[43m\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m    701\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m Bunch(\n\u001B[0;32m    702\u001B[0m     data\u001B[38;5;241m=\u001B[39mX,\n\u001B[0;32m    703\u001B[0m     target\u001B[38;5;241m=\u001B[39my,\n\u001B[1;32m   (...)\u001B[0m\n\u001B[0;32m    707\u001B[0m     target_names\u001B[38;5;241m=\u001B[39mtarget_columns,\n\u001B[0;32m    708\u001B[0m )\n",
      "File \u001B[1;32mD:\\python\\python-3.12.4\\Lib\\site-packages\\sklearn\\datasets\\_openml.py:67\u001B[0m, in \u001B[0;36m_retry_with_clean_cache.<locals>.decorator.<locals>.wrapper\u001B[1;34m(*args, **kw)\u001B[0m\n\u001B[0;32m     65\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m f(\u001B[38;5;241m*\u001B[39margs, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkw)\n\u001B[0;32m     66\u001B[0m \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[1;32m---> 67\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mf\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43margs\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkw\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m     68\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m URLError:\n\u001B[0;32m     69\u001B[0m     \u001B[38;5;28;01mraise\u001B[39;00m\n",
      "File \u001B[1;32mD:\\python\\python-3.12.4\\Lib\\site-packages\\sklearn\\datasets\\_openml.py:519\u001B[0m, in \u001B[0;36m_load_arff_response\u001B[1;34m(url, data_home, parser, output_type, openml_columns_info, feature_names_to_select, target_names_to_select, shape, md5_checksum, n_retries, delay, read_csv_kwargs)\u001B[0m\n\u001B[0;32m    436\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21m_load_arff_response\u001B[39m(\n\u001B[0;32m    437\u001B[0m     url: \u001B[38;5;28mstr\u001B[39m,\n\u001B[0;32m    438\u001B[0m     data_home: Optional[\u001B[38;5;28mstr\u001B[39m],\n\u001B[1;32m   (...)\u001B[0m\n\u001B[0;32m    448\u001B[0m     read_csv_kwargs: Optional[Dict] \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;01mNone\u001B[39;00m,\n\u001B[0;32m    449\u001B[0m ):\n\u001B[0;32m    450\u001B[0m \u001B[38;5;250m    \u001B[39m\u001B[38;5;124;03m\"\"\"Load the ARFF data associated with the OpenML URL.\u001B[39;00m\n\u001B[0;32m    451\u001B[0m \n\u001B[0;32m    452\u001B[0m \u001B[38;5;124;03m    In addition of loading the data, this function will also check the\u001B[39;00m\n\u001B[1;32m   (...)\u001B[0m\n\u001B[0;32m    517\u001B[0m \u001B[38;5;124;03m        `output_array_type == \"pandas\"`.\u001B[39;00m\n\u001B[0;32m    518\u001B[0m \u001B[38;5;124;03m    \"\"\"\u001B[39;00m\n\u001B[1;32m--> 519\u001B[0m     gzip_file \u001B[38;5;241m=\u001B[39m \u001B[43m_open_openml_url\u001B[49m\u001B[43m(\u001B[49m\u001B[43murl\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mdata_home\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mn_retries\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mn_retries\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mdelay\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mdelay\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m    520\u001B[0m     \u001B[38;5;28;01mwith\u001B[39;00m closing(gzip_file):\n\u001B[0;32m    521\u001B[0m         md5 \u001B[38;5;241m=\u001B[39m hashlib\u001B[38;5;241m.\u001B[39mmd5()\n",
      "File \u001B[1;32mD:\\python\\python-3.12.4\\Lib\\site-packages\\sklearn\\datasets\\_openml.py:183\u001B[0m, in \u001B[0;36m_open_openml_url\u001B[1;34m(openml_path, data_home, n_retries, delay)\u001B[0m\n\u001B[0;32m    181\u001B[0m                 opener \u001B[38;5;241m=\u001B[39m gzip\u001B[38;5;241m.\u001B[39mGzipFile\n\u001B[0;32m    182\u001B[0m             \u001B[38;5;28;01mwith\u001B[39;00m opener(os\u001B[38;5;241m.\u001B[39mpath\u001B[38;5;241m.\u001B[39mjoin(tmpdir, file_name), \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mwb\u001B[39m\u001B[38;5;124m\"\u001B[39m) \u001B[38;5;28;01mas\u001B[39;00m fdst:\n\u001B[1;32m--> 183\u001B[0m                 \u001B[43mshutil\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mcopyfileobj\u001B[49m\u001B[43m(\u001B[49m\u001B[43mfsrc\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mfdst\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m    184\u001B[0m         shutil\u001B[38;5;241m.\u001B[39mmove(fdst\u001B[38;5;241m.\u001B[39mname, local_path)\n\u001B[0;32m    185\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m \u001B[38;5;167;01mException\u001B[39;00m:\n",
      "File \u001B[1;32mD:\\python\\python-3.12.4\\Lib\\shutil.py:203\u001B[0m, in \u001B[0;36mcopyfileobj\u001B[1;34m(fsrc, fdst, length)\u001B[0m\n\u001B[0;32m    201\u001B[0m fsrc_read \u001B[38;5;241m=\u001B[39m fsrc\u001B[38;5;241m.\u001B[39mread\n\u001B[0;32m    202\u001B[0m fdst_write \u001B[38;5;241m=\u001B[39m fdst\u001B[38;5;241m.\u001B[39mwrite\n\u001B[1;32m--> 203\u001B[0m \u001B[38;5;28;01mwhile\u001B[39;00m buf \u001B[38;5;241m:=\u001B[39m \u001B[43mfsrc_read\u001B[49m\u001B[43m(\u001B[49m\u001B[43mlength\u001B[49m\u001B[43m)\u001B[49m:\n\u001B[0;32m    204\u001B[0m     fdst_write(buf)\n",
      "File \u001B[1;32mD:\\python\\python-3.12.4\\Lib\\http\\client.py:473\u001B[0m, in \u001B[0;36mHTTPResponse.read\u001B[1;34m(self, amt)\u001B[0m\n\u001B[0;32m    470\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;124mb\u001B[39m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124m\"\u001B[39m\n\u001B[0;32m    472\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mchunked:\n\u001B[1;32m--> 473\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_read_chunked\u001B[49m\u001B[43m(\u001B[49m\u001B[43mamt\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m    475\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m amt \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m:\n\u001B[0;32m    476\u001B[0m     \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mlength \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m \u001B[38;5;129;01mand\u001B[39;00m amt \u001B[38;5;241m>\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mlength:\n\u001B[0;32m    477\u001B[0m         \u001B[38;5;66;03m# clip the read to the \"end of response\"\u001B[39;00m\n",
      "File \u001B[1;32mD:\\python\\python-3.12.4\\Lib\\http\\client.py:601\u001B[0m, in \u001B[0;36mHTTPResponse._read_chunked\u001B[1;34m(self, amt)\u001B[0m\n\u001B[0;32m    598\u001B[0m     \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mchunk_left \u001B[38;5;241m=\u001B[39m chunk_left \u001B[38;5;241m-\u001B[39m amt\n\u001B[0;32m    599\u001B[0m     \u001B[38;5;28;01mbreak\u001B[39;00m\n\u001B[1;32m--> 601\u001B[0m value\u001B[38;5;241m.\u001B[39mappend(\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_safe_read\u001B[49m\u001B[43m(\u001B[49m\u001B[43mchunk_left\u001B[49m\u001B[43m)\u001B[49m)\n\u001B[0;32m    602\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m amt \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m:\n\u001B[0;32m    603\u001B[0m     amt \u001B[38;5;241m-\u001B[39m\u001B[38;5;241m=\u001B[39m chunk_left\n",
      "File \u001B[1;32mD:\\python\\python-3.12.4\\Lib\\http\\client.py:640\u001B[0m, in \u001B[0;36mHTTPResponse._safe_read\u001B[1;34m(self, amt)\u001B[0m\n\u001B[0;32m    633\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21m_safe_read\u001B[39m(\u001B[38;5;28mself\u001B[39m, amt):\n\u001B[0;32m    634\u001B[0m \u001B[38;5;250m    \u001B[39m\u001B[38;5;124;03m\"\"\"Read the number of bytes requested.\u001B[39;00m\n\u001B[0;32m    635\u001B[0m \n\u001B[0;32m    636\u001B[0m \u001B[38;5;124;03m    This function should be used when <amt> bytes \"should\" be present for\u001B[39;00m\n\u001B[0;32m    637\u001B[0m \u001B[38;5;124;03m    reading. If the bytes are truly not available (due to EOF), then the\u001B[39;00m\n\u001B[0;32m    638\u001B[0m \u001B[38;5;124;03m    IncompleteRead exception can be used to detect the problem.\u001B[39;00m\n\u001B[0;32m    639\u001B[0m \u001B[38;5;124;03m    \"\"\"\u001B[39;00m\n\u001B[1;32m--> 640\u001B[0m     data \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mfp\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mread\u001B[49m\u001B[43m(\u001B[49m\u001B[43mamt\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m    641\u001B[0m     \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28mlen\u001B[39m(data) \u001B[38;5;241m<\u001B[39m amt:\n\u001B[0;32m    642\u001B[0m         \u001B[38;5;28;01mraise\u001B[39;00m IncompleteRead(data, amt\u001B[38;5;241m-\u001B[39m\u001B[38;5;28mlen\u001B[39m(data))\n",
      "File \u001B[1;32mD:\\python\\python-3.12.4\\Lib\\socket.py:708\u001B[0m, in \u001B[0;36mSocketIO.readinto\u001B[1;34m(self, b)\u001B[0m\n\u001B[0;32m    706\u001B[0m \u001B[38;5;28;01mwhile\u001B[39;00m \u001B[38;5;28;01mTrue\u001B[39;00m:\n\u001B[0;32m    707\u001B[0m     \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[1;32m--> 708\u001B[0m         \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_sock\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mrecv_into\u001B[49m\u001B[43m(\u001B[49m\u001B[43mb\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m    709\u001B[0m     \u001B[38;5;28;01mexcept\u001B[39;00m timeout:\n\u001B[0;32m    710\u001B[0m         \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_timeout_occurred \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;01mTrue\u001B[39;00m\n",
      "File \u001B[1;32mD:\\python\\python-3.12.4\\Lib\\ssl.py:1252\u001B[0m, in \u001B[0;36mSSLSocket.recv_into\u001B[1;34m(self, buffer, nbytes, flags)\u001B[0m\n\u001B[0;32m   1248\u001B[0m     \u001B[38;5;28;01mif\u001B[39;00m flags \u001B[38;5;241m!=\u001B[39m \u001B[38;5;241m0\u001B[39m:\n\u001B[0;32m   1249\u001B[0m         \u001B[38;5;28;01mraise\u001B[39;00m \u001B[38;5;167;01mValueError\u001B[39;00m(\n\u001B[0;32m   1250\u001B[0m           \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mnon-zero flags not allowed in calls to recv_into() on \u001B[39m\u001B[38;5;132;01m%s\u001B[39;00m\u001B[38;5;124m\"\u001B[39m \u001B[38;5;241m%\u001B[39m\n\u001B[0;32m   1251\u001B[0m           \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m\u001B[38;5;18m__class__\u001B[39m)\n\u001B[1;32m-> 1252\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mread\u001B[49m\u001B[43m(\u001B[49m\u001B[43mnbytes\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mbuffer\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m   1253\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[0;32m   1254\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28msuper\u001B[39m()\u001B[38;5;241m.\u001B[39mrecv_into(buffer, nbytes, flags)\n",
      "File \u001B[1;32mD:\\python\\python-3.12.4\\Lib\\ssl.py:1104\u001B[0m, in \u001B[0;36mSSLSocket.read\u001B[1;34m(self, len, buffer)\u001B[0m\n\u001B[0;32m   1102\u001B[0m \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[0;32m   1103\u001B[0m     \u001B[38;5;28;01mif\u001B[39;00m buffer \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m:\n\u001B[1;32m-> 1104\u001B[0m         \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_sslobj\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mread\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;28;43mlen\u001B[39;49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mbuffer\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m   1105\u001B[0m     \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[0;32m   1106\u001B[0m         \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_sslobj\u001B[38;5;241m.\u001B[39mread(\u001B[38;5;28mlen\u001B[39m)\n",
      "\u001B[1;31mKeyboardInterrupt\u001B[0m: "
     ]
    }
   ],
   "execution_count": 4
  },
  {
   "metadata": {},
   "cell_type": "code",
   "outputs": [],
   "execution_count": null,
   "source": [
    "from sklearn.linear_model import LogisticRegression\n",
    "from sklearn.metrics import accuracy_score, precision_recall_fscore_support, confusion_matrix, classification_report\n",
    "import seaborn as sns\n",
    "import matplotlib.pyplot as plt\n",
    "import pandas as pd\n",
    "# Logistic回归模型训练\n",
    "model = LogisticRegression(max_iter=1000)\n",
    "model.fit(X_train, y_train)\n",
    "\n",
    "# 模型预测\n",
    "y_pred = model.predict(X_test)\n",
    "\n",
    "# 模型评估\n",
    "accuracy = accuracy_score(y_test, y_pred)\n",
    "precision, recall, f1, _ = precision_recall_fscore_support(y_test, y_pred, average='weighted')\n",
    "conf_matrix = confusion_matrix(y_test, y_pred)\n",
    "class_report = classification_report(y_test, y_pred)\n",
    "\n",
    "print(\"Accuracy:\", accuracy)\n",
    "print(\"Precision:\", precision)\n",
    "print(\"Recall:\", recall)\n",
    "print(\"F1-score:\", f1)\n",
    "print(\"Confusion Matrix:\\n\", conf_matrix)\n",
    "print(\"Classification Report:\\n\", class_report)\n",
    "\n",
    "# 使用热度图展示混淆矩阵，体现每个类别的识别效果\n",
    "plt.figure(figsize=(10,7))\n",
    "sns.heatmap(conf_matrix, annot=True, fmt='d', cmap='Blues')\n",
    "plt.title(\"Logistic Regression - Confusion Matrix\")\n",
    "plt.xlabel(\"Predicted Label\")\n",
    "plt.ylabel(\"True Label\")\n",
    "plt.show()"
   ],
   "id": "1b04074cd7a32c62"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-04-11T02:03:39.057106Z",
     "start_time": "2025-04-11T02:03:39.042868Z"
    }
   },
   "cell_type": "code",
   "source": [
    "import numpy as np\n",
    "import struct\n",
    "import os\n",
    "\n",
    "\n",
    "def read_idx(filename):\n",
    "   \n",
    "    with open(filename, 'rb') as f:\n",
    "       \n",
    "        zero, data_type_code, num_dimensions = struct.unpack('>HBB', f.read(4))\n",
    "        if data_type_code == 0x08:\n",
    "            dtype = np.uint8\n",
    "        else:\n",
    "            # 可以根据需要添加对其他类型的支持\n",
    "            raise ValueError(f\"Unsupported data type code: {data_type_code}\")\n",
    "\n",
    "        # 读取每个维度的大小\n",
    "        # '>' + 'I' * num_dimensions 表示读取 num_dimensions 个大端序的 4 字节无符号整数\n",
    "        dimension_sizes = struct.unpack(\n",
    "            '>' + 'I' * num_dimensions, f.read(4 * num_dimensions))\n",
    "\n",
    "        # 计算数据总大小\n",
    "        total_size = np.prod(dimension_sizes)\n",
    "\n",
    "        # 读取数据部分\n",
    "        data = np.frombuffer(f.read(total_size), dtype=dtype)\n",
    "\n",
    "        # 将数据重塑为正确的维度\n",
    "        data = data.reshape(dimension_sizes)\n",
    "\n",
    "        return data\n",
    "\n",
    "\n",
    "data_dir = r'./data/'  # 使用原始字符串避免转义问题\n",
    "\n",
    "# 检查路径是否存在\n",
    "if not os.path.isdir(data_dir):\n",
    "    print(f\"错误：目录不存在 - {data_dir}\")\n",
    "else:\n",
    "    try:\n",
    "        # --- 加载训练数据 ---\n",
    "        train_images_path = os.path.join(data_dir, 'train-images.idx3-ubyte')\n",
    "        train_labels_path = os.path.join(data_dir, 'train-labels.idx1-ubyte')\n",
    "\n",
    "        if os.path.exists(train_images_path) and os.path.exists(train_labels_path):\n",
    "            print(\"正在加载训练数据...\")\n",
    "            X_train = read_idx(train_images_path)\n",
    "            y_train = read_idx(train_labels_path)\n",
    "            print(f\"训练图像形状: {X_train.shape}\")  # 应该是 (60000, 28, 28)\n",
    "            print(f\"训练标签形状: {y_train.shape}\")  # 应该是 (60000,)\n",
    "        else:\n",
    "            print(\"警告：训练数据文件未找到。\")\n",
    "\n",
    "        # --- 加载测试数据 ---\n",
    "        test_images_path = os.path.join(data_dir, 't10k-images.idx3-ubyte')\n",
    "        test_labels_path = os.path.join(data_dir, 't10k-labels.idx1-ubyte')\n",
    "\n",
    "        if os.path.exists(test_images_path) and os.path.exists(test_labels_path):\n",
    "            print(\"\\n正在加载测试数据...\")\n",
    "            X_test = read_idx(test_images_path)\n",
    "            y_test = read_idx(test_labels_path)\n",
    "            print(f\"测试图像形状: {X_test.shape}\")  # 应该是 (10000, 28, 28)\n",
    "            print(f\"测试标签形状: {y_test.shape}\")  # 应该是 (10000,)\n",
    "        else:\n",
    "            print(\"警告：测试数据文件未找到。\")\n",
    "\n",
    "        # 使用 X_train, y_train, X_test, y_test 进行后续处理 ---\n",
    "        # 将图像数据展平\n",
    "        if 'X_train' in locals():\n",
    "            X_train_flat = X_train.reshape(X_train.shape[0], -1)\n",
    "            print(f\"\\n展平后的训练图像形状: {X_train_flat.shape}\")  # (60000, 784)\n",
    "        if 'X_test' in locals():\n",
    "            X_test_flat = X_test.reshape(X_test.shape[0], -1)\n",
    "            print(f\"展平后的测试图像形状: {X_test_flat.shape}\")  # (10000, 784)\n",
    "\n",
    "        # 将它们包装成 scikit-learn 的 Bunch 对象\n",
    "        from sklearn.utils import Bunch\n",
    "\n",
    "        mnist_data = Bunch(\n",
    "            data=X_train_flat if 'X_train_flat' in locals() else None,\n",
    "            target=y_train if 'y_train' in locals() else None,\n",
    "            images=X_train if 'X_train' in locals() else None,\n",
    "            DESCR=\"手动加载的 MNIST 训练数据\",\n",
    "            data_test=X_test_flat if 'X_test_flat' in locals() else None,\n",
    "            target_test=y_test if 'y_test' in locals() else None,\n",
    "            images_test=X_test if 'X_test' in locals() else None\n",
    "        )\n",
    "        print(mnist_data.keys())\n",
    "\n",
    "    except FileNotFoundError as e:\n",
    "        print(f\"错误：文件未找到 - {e}\")\n",
    "    except Exception as e:\n",
    "        print(f\"发生错误: {e}\")\n",
    "X_train = None\n",
    "y_train = None\n",
    "X_test = None\n",
    "y_test = None\n",
    "\n",
    "try:\n",
    "    print(\"正在加载数据...\")\n",
    "    train_images_path = os.path.join(data_dir, 'train-images.idx3-ubyte')\n",
    "    train_labels_path = os.path.join(data_dir, 'train-labels.idx1-ubyte')\n",
    "    test_images_path = os.path.join(data_dir, 't10k-images.idx3-ubyte')\n",
    "    test_labels_path = os.path.join(data_dir, 't10k-labels.idx1-ubyte')\n",
    "\n",
    "    required_files = [train_images_path, train_labels_path,\n",
    "                      test_images_path, test_labels_path]\n",
    "    all_files_exist = True\n",
    "    for f_path in required_files:\n",
    "        if not os.path.exists(f_path):\n",
    "            print(f\"错误：文件未找到 - {f_path}\")\n",
    "            all_files_exist = False\n",
    "\n",
    "    if all_files_exist:\n",
    "        X_train = read_idx(train_images_path)\n",
    "        y_train = read_idx(train_labels_path)\n",
    "        X_test = read_idx(test_images_path)\n",
    "        y_test = read_idx(test_labels_path)\n",
    "        print(\"数据加载完成。\")\n",
    "        print(f\"训练图像形状: {X_train.shape}\")\n",
    "        print(f\"训练标签形状: {y_train.shape}\")\n",
    "        print(f\"测试图像形状: {X_test.shape}\")\n",
    "        print(f\"测试标签形状: {y_test.shape}\")\n",
    "    else:\n",
    "        print(\"数据加载失败，缺少文件。\")\n",
    "        exit()  # Exit if data loading failed\n",
    "\n",
    "except Exception as e:\n",
    "    print(f\"加载数据时发生错误: {e}\")\n",
    "    exit()"
   ],
   "id": "34845fbad5729e12",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "错误：目录不存在 - ./data/\n",
      "正在加载数据...\n",
      "错误：文件未找到 - ./data/train-images.idx3-ubyte\n",
      "错误：文件未找到 - ./data/train-labels.idx1-ubyte\n",
      "错误：文件未找到 - ./data/t10k-images.idx3-ubyte\n",
      "错误：文件未找到 - ./data/t10k-labels.idx1-ubyte\n",
      "数据加载失败，缺少文件。\n"
     ]
    }
   ],
   "execution_count": 1
  },
  {
   "metadata": {},
   "cell_type": "code",
   "outputs": [],
   "execution_count": null,
   "source": [
    "X_train = None\n",
    "y_train = None\n",
    "X_test = None\n",
    "y_test = None\n",
    "\n",
    "try:\n",
    "    print(\"正在加载数据...\")\n",
    "    train_images_path = os.path.join(data_dir, 'train-images.idx3-ubyte')\n",
    "    train_labels_path = os.path.join(data_dir, 'train-labels.idx1-ubyte')\n",
    "    test_images_path = os.path.join(data_dir, 't10k-images.idx3-ubyte')\n",
    "    test_labels_path = os.path.join(data_dir, 't10k-labels.idx1-ubyte')\n",
    "\n",
    "    required_files = [train_images_path, train_labels_path,\n",
    "                      test_images_path, test_labels_path]\n",
    "    all_files_exist = True\n",
    "    for f_path in required_files:\n",
    "        if not os.path.exists(f_path):\n",
    "            print(f\"错误：文件未找到 - {f_path}\")\n",
    "            all_files_exist = False\n",
    "\n",
    "    if all_files_exist:\n",
    "        X_train = read_idx(train_images_path)\n",
    "        y_train = read_idx(train_labels_path)\n",
    "        X_test = read_idx(test_images_path)\n",
    "        y_test = read_idx(test_labels_path)\n",
    "        print(\"数据加载完成。\")\n",
    "        print(f\"训练图像形状: {X_train.shape}\")\n",
    "        print(f\"训练标签形状: {y_train.shape}\")\n",
    "        print(f\"测试图像形状: {X_test.shape}\")\n",
    "        print(f\"测试标签形状: {y_test.shape}\")\n",
    "    else:\n",
    "        print(\"数据加载失败，缺少文件。\")\n",
    "        exit()  # Exit if data loading failed\n",
    "\n",
    "except Exception as e:\n",
    "    print(f\"加载数据时发生错误: {e}\")\n",
    "    exit()"
   ],
   "id": "bbb3c1bf9b61d66b"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-05-07T07:54:45.364255Z",
     "start_time": "2025-05-07T07:54:44.842043Z"
    }
   },
   "cell_type": "code",
   "source": [
    "import numpy as np\n",
    "from CNNModel import CNN\n",
    "import matplotlib.pyplot as plt\n",
    "import time\n",
    "from sklearn.datasets import fetch_openml\n",
    "from sklearn.model_selection import train_test_split\n",
    "from sklearn.preprocessing import LabelEncoder\n",
    "\n",
    "# 设置中文显示\n",
    "import matplotlib as mpl\n",
    "\n",
    "plt.rcParams[\"font.sans-serif\"] = [\"SimHei\"]  # 设置中文字体为黑体\n",
    "plt.rcParams[\"axes.unicode_minus\"] = False  # 解决负号显示问题\n",
    "\n",
    "\n",
    "def load_mnist_dataset(sample_size=1000):\n",
    "    \"\"\"\n",
    "    加载MNIST数据集\n",
    "\n",
    "    参数:\n",
    "    sample_size -- 如果提供，限制数据集大小为指定值\n",
    "\n",
    "    返回:\n",
    "    X_train, X_test, y_train, y_test -- 训练集和测试集\n",
    "    \"\"\"\n",
    "    print(\"正在加载MNIST数据集...\")\n",
    "    # 从OpenML加载MNIST数据集\n",
    "    X, y = fetch_openml(\"mnist_784\", version=1, return_X_y=True, as_frame=False)\n",
    "\n",
    "    # 将数据限制到指定大小\n",
    "    if sample_size and sample_size < len(X):\n",
    "        X = X[:sample_size]\n",
    "        y = y[:sample_size]\n",
    "\n",
    "    # 标准化数据\n",
    "    X = X / 255.0\n",
    "\n",
    "    # 将标签从字符转为整数\n",
    "    label_encoder = LabelEncoder()\n",
    "    y = label_encoder.fit_transform(y)\n",
    "\n",
    "    # 重塑数据为 (样本数, 通道数, 高度, 宽度)\n",
    "    X = X.reshape(-1, 1, 28, 28)\n",
    "\n",
    "    # 分割为训练集和测试集\n",
    "    X_train, X_test, y_train, y_test = train_test_split(\n",
    "        X, y, test_size=0.2, random_state=42\n",
    "    )\n",
    "\n",
    "    print(f\"数据加载完成：{X_train.shape[0]} 个训练样本，{X_test.shape[0]} 个测试样本\")\n",
    "    return X_train, X_test, y_train, y_test\n",
    "\n",
    "\n",
    "def evaluate_model(model, X_test, y_test, batch_size=32):\n",
    "    \"\"\"\n",
    "    评估模型在测试集上的表现\n",
    "\n",
    "    参数:\n",
    "    model -- CNN模型实例\n",
    "    X_test -- 测试数据\n",
    "    y_test -- 测试标签\n",
    "    batch_size -- 批量大小\n",
    "\n",
    "    返回:\n",
    "    accuracy -- 准确率\n",
    "    \"\"\"\n",
    "    num_samples = X_test.shape[0]\n",
    "    correct = 0\n",
    "\n",
    "    # 分批进行预测以处理大型数据集\n",
    "    for i in range(0, num_samples, batch_size):\n",
    "        end = min(i + batch_size, num_samples)\n",
    "        X_batch = X_test[i:end]\n",
    "        y_batch = y_test[i:end]\n",
    "\n",
    "        # 获取预测结果\n",
    "        predictions = model.predict(X_batch)\n",
    "\n",
    "        # 计算准确的预测数量\n",
    "        correct += np.sum(predictions == y_batch)\n",
    "\n",
    "    # 计算准确率\n",
    "    accuracy = correct / num_samples\n",
    "    return accuracy\n",
    "\n",
    "\n",
    "def visualize_results(losses, accuracies, save_path=None):\n",
    "    \"\"\"\n",
    "    可视化训练结果\n",
    "\n",
    "    参数:\n",
    "    losses -- 每个epoch的损失列表\n",
    "    accuracies -- 每个epoch的准确率列表\n",
    "    save_path -- 保存图像的路径，如果是None则显示图像\n",
    "    \"\"\"\n",
    "    epochs = range(1, len(losses) + 1)\n",
    "\n",
    "    plt.figure(figsize=(12, 5))\n",
    "\n",
    "    # 绘制损失曲线\n",
    "    plt.subplot(1, 2, 1)\n",
    "    plt.plot(epochs, losses, \"b-\", label=\"训练损失\")\n",
    "    plt.title(\"训练损失\")\n",
    "    plt.xlabel(\"Epochs\")\n",
    "    plt.ylabel(\"损失\")\n",
    "    plt.legend()\n",
    "\n",
    "    # 绘制准确率曲线\n",
    "    plt.subplot(1, 2, 2)\n",
    "    plt.plot(epochs, accuracies, \"r-\", label=\"测试准确率\")\n",
    "    plt.title(\"测试准确率\")\n",
    "    plt.xlabel(\"Epochs\")\n",
    "    plt.ylabel(\"准确率\")\n",
    "    plt.legend()\n",
    "\n",
    "    plt.tight_layout()\n",
    "\n",
    "    if save_path:\n",
    "        plt.savefig(save_path)\n",
    "    else:\n",
    "        plt.show()\n",
    "\n",
    "\n",
    "def visualize_predictions(model, X_test, y_test, num_samples=5):\n",
    "    \"\"\"\n",
    "    可视化预测结果\n",
    "\n",
    "    参数:\n",
    "    model -- CNN模型实例\n",
    "    X_test -- 测试数据\n",
    "    y_test -- 测试标签\n",
    "    num_samples -- 显示样本数量\n",
    "    \"\"\"\n",
    "    # 随机选择样本\n",
    "    indices = np.random.choice(len(X_test), num_samples, replace=False)\n",
    "\n",
    "    plt.figure(figsize=(15, 3))\n",
    "\n",
    "    for i, idx in enumerate(indices):\n",
    "        # 获取样本\n",
    "        image = X_test[idx, 0]  # 取出第一个通道\n",
    "        true_label = y_test[idx]\n",
    "\n",
    "        # 获取预测\n",
    "        prediction = model.predict(X_test[idx : idx + 1])[0]\n",
    "\n",
    "        # 显示图像和标签\n",
    "        plt.subplot(1, num_samples, i + 1)\n",
    "        plt.imshow(image, cmap=\"gray\")\n",
    "        plt.title(f\"预测: {prediction}\\n真实: {true_label}\")\n",
    "        plt.axis(\"off\")\n",
    "\n",
    "    plt.tight_layout()\n",
    "    plt.show()\n",
    "\n",
    "\n",
    "def main():\n",
    "    # 加载数据集（使用合适大小的样本以平衡速度和效果）\n",
    "    X_train, X_test, y_train, y_test = load_mnist_dataset(sample_size=1000)\n",
    "\n",
    "    # 设置训练参数\n",
    "    batch_size = 32  # 更小的批量大小\n",
    "    epochs = 3  # 减少轮次\n",
    "    learning_rate = 0.01\n",
    "\n",
    "    # 初始化CNN模型\n",
    "    input_shape = (1, 28, 28)  # 单通道，28x28像素\n",
    "    num_classes = 10  # MNIST有10个类别（数字0-9）\n",
    "    model = CNN(input_shape=input_shape, num_classes=num_classes)\n",
    "\n",
    "    print(\"开始训练模型...\")\n",
    "    start_time = time.time()\n",
    "\n",
    "    # 训练模型并记录损失和准确率\n",
    "    losses = []\n",
    "    accuracies = []\n",
    "\n",
    "    for epoch in range(epochs):\n",
    "        # 训练一个epoch\n",
    "        print(f\"Epoch {epoch+1}/{epochs} 训练中...\")\n",
    "        num_samples = X_train.shape[0]\n",
    "        total_loss = 0\n",
    "\n",
    "        # 打乱数据\n",
    "        indices = np.random.permutation(num_samples)\n",
    "        X_shuffled = X_train[indices]\n",
    "        y_shuffled = y_train[indices]\n",
    "\n",
    "        # 批量训练\n",
    "        for i in range(0, num_samples, batch_size):\n",
    "            end = min(i + batch_size, num_samples)\n",
    "            X_batch = X_shuffled[i:end]\n",
    "            y_batch = y_shuffled[i:end]\n",
    "\n",
    "            # 前向传播计算损失\n",
    "            loss = model.forward(X_batch, y_batch)\n",
    "            total_loss += loss\n",
    "\n",
    "            # 反向传播更新参数\n",
    "            model.backward(learning_rate)\n",
    "\n",
    "            # 只打印部分进度以减少输出\n",
    "            if (i // batch_size) % 5 == 0:\n",
    "                print(\n",
    "                    f\"Batch {i//batch_size+1}/{num_samples//batch_size+1}, Loss: {loss:.4f}\"\n",
    "                )\n",
    "\n",
    "        # 计算平均损失\n",
    "        avg_loss = total_loss / (num_samples // batch_size)\n",
    "        losses.append(avg_loss)\n",
    "\n",
    "        # 评估模型\n",
    "        accuracy = evaluate_model(model, X_test, y_test, batch_size)\n",
    "        accuracies.append(accuracy)\n",
    "\n",
    "        print(\n",
    "            f\"Epoch {epoch+1}/{epochs}, Loss: {avg_loss:.4f}, Accuracy: {accuracy:.4f}\"\n",
    "        )\n",
    "\n",
    "    # 计算总训练时间\n",
    "    training_time = time.time() - start_time\n",
    "    print(f\"训练完成！总时间: {training_time:.2f} 秒\")\n",
    "\n",
    "    # 最终评估\n",
    "    final_accuracy = evaluate_model(model, X_test, y_test, batch_size)\n",
    "    print(f\"最终测试准确率: {final_accuracy:.4f}\")\n",
    "\n",
    "    # 可视化训练结果\n",
    "    visualize_results(losses, accuracies)\n",
    "\n",
    "    # 可视化预测结果\n",
    "    visualize_predictions(model, X_test, y_test)\n",
    "\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    main()"
   ],
   "id": "20fd81fe640beb14",
   "outputs": [
    {
     "ename": "ModuleNotFoundError",
     "evalue": "No module named 'CNNModel'",
     "output_type": "error",
     "traceback": [
      "\u001B[1;31m---------------------------------------------------------------------------\u001B[0m",
      "\u001B[1;31mModuleNotFoundError\u001B[0m                       Traceback (most recent call last)",
      "Cell \u001B[1;32mIn[1], line 2\u001B[0m\n\u001B[0;32m      1\u001B[0m \u001B[38;5;28;01mimport\u001B[39;00m \u001B[38;5;21;01mnumpy\u001B[39;00m \u001B[38;5;28;01mas\u001B[39;00m \u001B[38;5;21;01mnp\u001B[39;00m\n\u001B[1;32m----> 2\u001B[0m \u001B[38;5;28;01mfrom\u001B[39;00m \u001B[38;5;21;01mCNNModel\u001B[39;00m \u001B[38;5;28;01mimport\u001B[39;00m CNN\n\u001B[0;32m      3\u001B[0m \u001B[38;5;28;01mimport\u001B[39;00m \u001B[38;5;21;01mmatplotlib\u001B[39;00m\u001B[38;5;21;01m.\u001B[39;00m\u001B[38;5;21;01mpyplot\u001B[39;00m \u001B[38;5;28;01mas\u001B[39;00m \u001B[38;5;21;01mplt\u001B[39;00m\n\u001B[0;32m      4\u001B[0m \u001B[38;5;28;01mimport\u001B[39;00m \u001B[38;5;21;01mtime\u001B[39;00m\n",
      "\u001B[1;31mModuleNotFoundError\u001B[0m: No module named 'CNNModel'"
     ]
    }
   ],
   "execution_count": 1
  },
  {
   "metadata": {},
   "cell_type": "code",
   "outputs": [],
   "execution_count": null,
   "source": "",
   "id": "c629f7c4fc02f8e6"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
